using System;

namespace Legalsoft.Truffer
{
    public class Matrix<T> : System.IDisposable
    {
        private int nn { get; set; } = 0;
        private int mm { get; set; } = 0;
        private T[] v { get; set; } = null;

        public Matrix()
        {
            this.nn = 0;
            this.mm = 0;
            this.v = null;
        }

        public Matrix(int n, int m)
        {
            this.nn = n;
            this.mm = m;
            this.v = (nn * mm) > 0 ? new T[nn * mm] : null;
        }

        public Matrix(int n, int m, T a)
        {
            this.nn = n;
            this.mm = m;
            this.v = (nn * mm) > 0 ? new T[nn * mm] : null;
            int nel = nn * mm;
            for (int i = 0; i < nel; i++)
            {
                v[i] = a;
            }
        }

        public Matrix(int n, int m, T[] a)
        {
            this.nn = n;
            this.mm = m;
            this.v = (nn * mm) > 0 ? new T[nn * mm] : null;
            int nel = nn * mm;
            for (int i = 0; i < nel; i++)
            {
                v[i] = a[i];
            }
        }

        public Matrix(Matrix<T> rhs)
        {
            this.nn = rhs.nn;
            this.mm = rhs.mm;
            this.v = (nn * mm) > 0 ? new T[nn * mm] : null;
            int nel = nn * mm;
            for (int i = 0; i < nel; i++)
            {
                v[i] = rhs[i];
            }
        }

        public T[] ToArray()
        {
            int dim = nn * mm;
            T[] r = new T[dim];
            for (int i = 0; i < dim; i++)
            {
                r[i] = this[i];
            }
            return r;
        }

        public void FromArray(T[] r)
        {
            for (int i = 0; i < r.Length && i < v.Length; i++)
            {
                this[i] = r[i];
            }
        }

        public Vector<T> GetRow(int row)
        {
            Vector<T> r = new Vector<T>(mm);
            for (int i = 0; i < mm; i++) r[i] = this[row, i];
            return r;
        }

        public Vector<T> GetColumn(int col)
        {
            Vector<T> r = new Vector<T>(nn);
            for (int i = 0; i < nn; i++) r[i] = this[i, col];
            return r;
        }

        public Matrix<T> CopyFrom(Matrix<T> rhs)
        {
            this.nn = rhs.nn;
            this.mm = rhs.mm;
            this.v = (nn * mm) > 0 ? new T[nn * mm] : null;
            int nel = nn * mm;
            for (int i = 0; i < nel; i++)
            {
                v[i] = rhs[i];
            }
            return this;
        }

        public T this[int i]
        {
            get
            {
#if _CHECKBOUNDS_
        if (i < 0 || i >= nn * mm)
        {
            throw ("Matrix subscript out of bounds");
        }
#endif
                return v[i];
            }
            set
            {
                v[i] = value;
            }
        }

        public T this[int i, int j]
        {
            get
            {
#if _CHECKBOUNDS_
        if (i < 0 || i >= nn * mm)
        {
            throw ("Matrix subscript out of bounds");
        }
#endif
                return v[i * mm + j];
            }
            set
            {
                v[i * mm + j] = value;
            }
        }

        public int nrows()
        {
            return nn;
        }

        public int ncols()
        {
            return mm;
        }

        public void resize(int newn, int newm)
        {
            this.nn = newn;
            this.mm = newm;
            this.v = (nn * mm) > 0 ? new T[nn * mm] : null;
        }

        public void assign(int newn, int newm, T a)
        {
            this.nn = newn;
            this.mm = newm;
            this.v = (nn * mm) > 0 ? new T[nn * mm] : null;
            int nel = nn * mm;
            for (int i = 0; i < nel; i++)
            {
                v[i] = a;
            }
        }

        public void Dispose()
        {
            if (v != null)
            {
                (v) = null;
            }
        }
    }
}
